﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SafeScript.MetaModel.Statements;

namespace SafeScript.MetaModel.Xml
{
    public class XmlTag : MetaModel.Statements.Operator
    {
        private static ITypeDef TagType;
        // private static ITypeDef EventType;
        // private static FunctionTypeDef EventFunctionTypeDef;

        public bool IsStatic;
        public AbstractType Type;

        /// <summary>
        /// Contains XmlText, Operators or other Tags
        /// </summary>
        public List<object> Content = new List<object>();
        public Dictionary<string, XmlAttribute> Attributes = new Dictionary<string, XmlAttribute>();

        public bool IsHTML
        {
            get
            {
                return this.Type.GetAttribute<HTMLClassAttribute>() != null;
            }
        }

        public string Name
        {
            get
            {
                return this.Type.Name;
            }
        }

        public override int GetPrecedence()
        {
            return 0;
        }

        public override ITypeDef ComputeType(ITypeDef goal, bool needsRef, ErrorReport report)
        {
            if (TagType == null)
            {
                TagType = Compiler.CurrentCompiler.LookupType("System.HTML.Node");
                if (TagType == null)
                {
                    report.Add(new ErrorEntry(this.Node) { Type = ErrorType.LibraryMissing, Info = "System.HTML" });
                    return null;
                }
            }

            if (!(this.Type is Class))
            {
                report.Add(new ErrorEntry(this.Node) { Type = ErrorType.NotAClass });
                return null;
            }

            if ((this.Type as Class).IsAbstract && this.Type.GetAttribute<HTMLClassAttribute>() == null)
            {
                report.Add(new ErrorEntry(this.Node) { Info = (this.Type as Class).Name, Type = ErrorType.InstantiationOfAbstractClass });
                return null;
            }

            Constructor ctor = (this.Type as Class).GetDefaultConstructor();
            if (ctor == null)
            {
                report.Add(new ErrorEntry(this.Node) { Info = (this.Type as Class).Name, Type = ErrorType.NoDefaultConstructor });
                return null;
            }

            for( int i = 0; i < this.Content.Count; ++i )
            {
                object obj = this.Content[i];
                if (obj is XmlText )
                    continue;
                if (obj is XmlTag)
                {
                    XmlTag tag = obj as XmlTag;
                    tag.ComputeType(null, false, report);
                }
                else if (obj is Operator)
                {
                    Operator op = obj as Operator;
                    ITypeDef ot = op.ComputeType(null, false, report);
                    // The type must either be string or a tag type
                    if ( ot.CanBeAssignedTo(StringType.Self) )
                    {
                        this.Content[i] = new XmlTextFunction() { Operator = op };
                    }
                    else if ( ot.CanBeAssignedTo(TagType) )
                    {
                        this.Content[i] = new XmlTagFunction() { Operator = op };
                    }
                    else
                    {
                        report.Add( new ErrorEntry(this.Node) { Type = ErrorType.IncompatibleTypes });
                        return null;
                    }
                }
                else
                    System.Diagnostics.Debug.Assert(false, "Unexpected content in XmlTag");
            }

            // Check the type of the values assigned to the XML attributes
            foreach (XmlAttribute attr in this.Attributes.Values)
            {
                // Assign to an event?
                if (attr.Event != null)
                {
                    // We expect a function that accepts the event object as parameter and returns void (or anything else)
                    FunctionTypeDef ft = new FunctionTypeDef();
                    ft.ReturnType = null;
                    ft.ParameterTypes.Add( attr.Event.EventType );
                    ITypeDef t = attr.OperatorValue.ComputeType(ft, false, report);
                    if (t == null)
                        return null;
                    InstanceType it = this.ToInstance(ref attr.OperatorValue, t, report);
                    if (it.Type is FunctionTypeDef)
                    {
                        // Fill in a return type. Otherwise it would crash. This is ok since
                        // we are not interested in the return type anyway.
                        ft.ReturnType = (it.Type as FunctionTypeDef).ReturnType;
                        if (!t.CanBeAssignedTo(ft))
                        {
                            // Perhaps the function takes void as an argument, that would be ok, too
                            ft.ParameterTypes.Clear();
                            if (!t.CanBeAssignedTo(ft))
                            {
                                report.Add(new ErrorEntry(attr.Node) { Type = ErrorType.IncompatibleTypes });
                                return null;
                            }
                        }
                    }
                    else
                    {
                        report.Add(new ErrorEntry(attr.Node) { Type = ErrorType.IncompatibleTypes });
                        return null;
                    }
                    // TODO: It could as well be some expression or a binding
                }
                else // We assign to a variable or property
                {
                    ITypeDef agoal = attr.Var.Type;
                    if (attr.Name == "style" && this.IsHTML)
                        agoal = new InstanceType() { Type = StringType.Self };
                    ITypeDef agoal2 = agoal is InstanceType ? (agoal as InstanceType).Type : agoal;

                    // We assign an expression?
                    if (attr.OperatorValue != null)
                    {
                        ITypeDef t = attr.OperatorValue.ComputeType(agoal, false, report);
                        if (t == null)
                            return null;
                        this.ToInstance(ref attr.OperatorValue, t, report);

                        if (!t.CanBeAssignedTo(agoal))
                        {
                            if (this.ConvertTo(ref attr.OperatorValue, t, agoal, report) == null)
                            {
                                report.Add(new ErrorEntry(attr.Node) { Type = ErrorType.IncompatibleTypes });
                                return null;
                            }
                        }
                    }
                    else // String
                    {
                        if (agoal is InstanceType)
                            agoal = (agoal as InstanceType).Type;
                        if (!(agoal is StringType) && agoal != Compiler.CurrentCompiler.LookupType("System.HTML.Style"))
                        {
                            report.Add(new ErrorEntry(attr.Node) { Type = ErrorType.IncompatibleTypes });
                            return null;
                        }
                    }
                }
            }

            return new InstanceType() { Type = this.Type };
        }
    }
}
